﻿
using FlexPlatform.Contracts.DataModel.Identification;
using System.Collections.Generic;
using System.Linq.Expressions;
using FlexPlatform.Contracts.DataModel;
using System;
using System.Data.Entity;
using System.Linq;
using FlexPlatform.Common;

namespace FlexPlatform.DataModel.DataAccess.Identification
{
    public class EntityIdentifier<T> : IEntityIdentifier<T> where T:IDbEntity
    {
        #region Private fields & Constructors

        private Dictionary<string, IIdentifyingFieldGroup<T>> clsGroups;
        private IIdentifyingFieldGroup<T> clsLastIdentifyingGroup;
        private IIdentifyingFieldGroup<T> clsCurrentIdentifyingGroup;

        public EntityIdentifier()
        {
            clsGroups = SystemGlobal.Configuration.GetEntityConfiguration<T>().Identification.IdentificationGroups;

        }

        #endregion

        public event EventHandler ReidentificationNeeded;

        public T DataEntity { get; set; }

        public void Process_OnPropertyChanged(string propertyName)
        {
            ComputeBestIdentifyingGroup(propertyName);

            if (clsCurrentIdentifyingGroup != null)
            {
                if (ReidentificationNeeded != null)
                {
                    ReidentificationNeeded(this, null);
                }
            }

        }

        private void ComputeBestIdentifyingGroup(string propertyName)
        {
            IIdentifyingFieldGroup<T> bestGroup = null;

            // get the group with the biggest priority that is capable of identification and also (eventually) contains the required property.
            foreach (var group in clsGroups.Values)
            {
                // 1. if there have not been any previous identification, best priority group wins, no mather whether it contains the changed field or not.
                // 2. if the propertyName is not provided, get the best priority group
                if ((string.IsNullOrEmpty(propertyName)) ||
                    ((clsLastIdentifyingGroup == null) || (group.ContainsField(propertyName))))
                {
                    if (group.CanIdentify(this.DataEntity))
                    {
                        bestGroup = group;
                        break;
                    }
                }
            }

            BestIdentifyingGroup = bestGroup;
        }

        private void ComputeBestIdentifyingGroup()
        {
            ComputeBestIdentifyingGroup(null);
        }



        private IIdentifyingFieldGroup<T> BestIdentifyingGroup
        {
            get
            {
                if (clsCurrentIdentifyingGroup == null)
                {
                    ComputeBestIdentifyingGroup();
                }
                return clsCurrentIdentifyingGroup;
            }
            set
            {
                clsLastIdentifyingGroup = clsCurrentIdentifyingGroup;
                clsCurrentIdentifyingGroup = value;
            }
        }
        



        public bool Identify(DbContext dataContext, out T foundEntity) 
        {
            IIdentifyingFieldGroup<T> bestGroup = this.BestIdentifyingGroup;
            foundEntity = default(T);

            if (bestGroup != null)
            {
                var actualType = this.DataEntity.GetType();
                var dbSet = dataContext.Set(actualType);
                var filter = this.BestIdentifyingGroup.GetFilter(this.DataEntity);

                if (dbSet.OfType<T>().Any(filter))
                {
                    foundEntity = dbSet.OfType<T>().First(filter);
                    return true;
                }
            }

            return false;
        }
    }
}
